; haribote-os boot asm
; TAB=4

BOTPAK	EQU		0x00280000		; 加载bootpack
DSKCAC	EQU		0x00100000		; 磁盘缓存的位置
DSKCAC0	EQU		0x00008000		; 磁盘缓存的位置（实模式）

; BOOT_INFO 相关
CYLS	EQU		0x0ff0			; 引导扇区设置		ipl10中已经将读磁盘的柱面数的值写到内存地址0x0ff0中
LEDS	EQU		0x0ff1
VMODE	EQU		0x0ff2			; 关于颜色的信息
SCRNX	EQU		0x0ff4			; 分辨率X
SCRNY	EQU		0x0ff6			; 分辨率Y
VRAM	EQU		0x0ff8			; 图像缓冲区的起始地址

		ORG		0xc200			;  这个的程序要被装载的内存地址

;	画面设置

		MOV		AL,0x13			; VGA显卡，320x200x8bit
		MOV		AH,0x00
		INT		0x10
		MOV		BYTE [VMODE],8	; 屏幕的模式（参考C语言的引用）
		MOV		WORD [SCRNX],320
		MOV		WORD [SCRNY],200
		MOV		DWORD [VRAM],0x000a0000

;	通过 BIOS 获取指示灯状态

		MOV		AH,0x02
		INT		0x16 			; keyboard BIOS
		MOV		[LEDS],AL

;	PIC关闭一切中断
;	根据AT兼容机的规格，如果要初始化PIC，
;	必须在CLI之前进行，否则有时会挂起。
;	随后进行PIC的初始化。

		MOV		AL,0xff		
		OUT		0x21,AL
		NOP						; 如果连续执行OUT指令，有些机种会无法正常运行  NOP指令什么都不做，它只是让CPU休息一个时钟长的时间。
		OUT		0xa1,AL

		CLI						; 禁止CPU级别的中断		如果当CPU进行模式转换时进来了中断信号，那可就麻烦了。而且，后来还要进行PIC的初始化，初始化时也不允许有中断发生。所以，我们要把中断全部屏蔽掉。

;	为了让CPU能够访问1MB以上的内存空间，设定A20GATE

		CALL	waitkbdout
		MOV		AL,0xd1
		OUT		0x64,AL
		CALL	waitkbdout
		MOV		AL,0xdf			; enable A20
		OUT		0x60,AL
		CALL	waitkbdout

;	切换到保护模式

[INSTRSET "i486p"]				; 说明使用486指令

		LGDT	[GDTR0]			; 设置临时GDT
		MOV		EAX,CR0
		AND		EAX,0x7fffffff	; 设bit31为0（禁用分页）
		OR		EAX,0x00000001	; bit0到1转换（保护模式过渡） 将CR0这一特殊的32位寄存器的值代入EAX，并将最高位置为0，最低位置为1，再将这个值返回给CR0寄存器。这样就完成了模式转换，进入到保护模式,在这种模式下，应用程序既不能随便改变段的设定，又不能使用操作系统专用的段。操作系统受到CPU的保护，所以称为保护模式。
		MOV		CR0,EAX			;CR0，也就是control register 0，是一个非常重要的寄存器，只有操作系统才能操作它。
		JMP		pipelineflush	;现代CPU为了提高执行效率，采用了指令管道技术，即在执行当前指令的同时，预先读取并部分处理后续的一条或多条指令。当CPU模式发生变化时（如从实模式切换到保护模式），已经预读的指令可能不再适用，因为指令的解码、执行环境已经改变。执行JMP指令强制CPU刷新指令管道，丢弃旧的预读指令，重新从新的位置按照新的模式解码执行指令。
pipelineflush:
		MOV		AX,1*8			;  可读写的段 32bit		在保护模式下，段选择子不是简单的序号，而是代表了全局描述符表（GDT）中段描述符的字节偏移    设置为1，意味着使用GDT中的第二个段描述符（索引从0开始）。第一个描述符（索引0）通常是不可用的，或者用作一个特殊的“空”描述符，出于安全和稳定性考虑，不用于常规的内存访问。
		MOV		DS,AX
		MOV		ES,AX
		MOV		FS,AX
		MOV		GS,AX
		MOV		SS,AX

; bootpack传递

		MOV		ESI,bootpack	; 转送源		
		MOV		EDI,BOTPAK		; 转送目标
		MOV		ECX,512*1024/4	;转送数据大小是以双字为单位的，所以数据大小用字节数除以4来指定 ，也就是一次传递DW的数据宽度
		CALL	memcpy			;从bootpack的地址开始的512KB内容复制到BOTPAK 0x00280000号地址去。 2M多的地方

; 磁盘数据最终转送到它本来的位置去
; 首先从启动扇区开始

		MOV		ESI,0x7c00		; 转送源		
		MOV		EDI,DSKCAC		; 转送目标
		MOV		ECX,512/4
		CALL	memcpy			;从0x7c00复制512字节到0x00100000。这正好是将启动扇区复制到1MB以后的内存去的意思。

; 剩余的全部

		MOV		ESI,DSKCAC0+512	; 转送源
		MOV		EDI,DSKCAC+512	; 转送源目标
		MOV		ECX,0
		MOV		CL,BYTE [CYLS]	; 柱面数为80
		IMUL	ECX,512*18*2/4	; 从柱面数变换为字节数/4		IMUL 指令用于执行带符号整数乘法。这条指令将 ECX 寄存器的当前值与操作数 512*18*2/4
		SUB		ECX,512/4		; 减去 IPL 偏移量
		CALL	memcpy			; 它的意思就是将始于0x00008200的磁盘内容，复制到0x00100200那里。也就是第二扇区（也就是从asmhead开始到c语言代码的所有）之后的扇区全部复制，其实和bootpack有重复。也就是从0x00100000 开始到0x00100200再到10个柱面，而不是80个柱面，即使最多复制1 440KB到0x00268000，也不会覆盖到BOTPAK0x00280000号地址位置的bootpack代码

; 必须由asmhead来完成的工作，至此全部完毕
; 以后就交由bootpack来完成

; bootpack启动

		MOV		EBX,BOTPAK
		MOV		ECX,[EBX+16]
		ADD		ECX,3			; ECX += 3;
		SHR		ECX,2			; ECX /= 4;		其实和TEST ECX作用基本一样
		JZ		skip			; 没有要转送的东西时
		MOV		ESI,[EBX+20]	; 转送源
		ADD		ESI,EBX
		MOV		EDI,[EBX+12]	; 转送目标
		CALL	memcpy			; 它对bootpack.hrb的header（头部内容）进行解析，将执行所必需的数据传送过去 它会将bootpack.hrb第0x0360字节开始的0x0030字节(直接到hrb尾部，也就是倒数三行)复制到0x00310000号地址去。也就是约为3MB的位置   其实就是c语言的编译之后的全局未初始化值的位置		即在这个区域 [栈及其他（1MB）]
;0x00000000 - 0x000fffff : 虽然在启动中会多次使用，但之后就变空。（1MB）
;0x00100000 - 0x00267fff : 用于保存软盘的内容。（1440KB）
;0x00268000 - 0x0026f7ff : 空（30KB）
;0x0026f800 - 0x0026ffff : IDT （2KB）
;0x00270000 - 0x0027ffff : GDT （64KB）
;0x00280000 - 0x002fffff : bootpack.hrb（512KB）
;0x00300000 - 0x003fffff : 栈及其他（1MB）
;0x00400000 - : 空

skip:
		MOV		ESP,[EBX+12]	; 堆栈的初始化
		JMP		DWORD 2*8:0x0000001b		; 这里的0x1b号地址是指第2个段的0x1b号地址。第2个段描述符的基地址是0x280000，所以实际上是从0x28001b开始执行的。这也就是bootpack.hrb的0x1b号地址。	0x1b号就是hrb应用程序（main.hrb就像是操作系统进程软件一样）的第一行代码位置

waitkbdout:					;如果控制器里有键盘代码，或者是已经累积了鼠标数据，就顺便把它们读取出来。循环等待
		IN		AL,0x64
		AND		AL,0x02
		IN		AL,0x60			; 空读（为了清空数据接收缓冲区中的垃圾数据）
		JNZ		waitkbdout	; AND的结果如果不是0，就跳到waitkbdout
		RET

memcpy:
		MOV		EAX,[ESI]
		ADD		ESI,4
		MOV		[EDI],EAX
		ADD		EDI,4
		SUB		ECX,1
		JNZ		memcpy			; 减法运算的结果如果不是0，就跳转到memcpy
		RET
; memcpy地址前缀大小

		ALIGNB	16		;ALIGNB 16：这是对齐指令，确保接下来的数据或指令在内存中以16字节为单位对齐。这可以提高某些处理器访问内存的效率。
GDT0:
		RESB	8				; GDT0也是一种特定的GDT。0号是空区域（null sector），不能够在那里定义段。将GDT的第0项设置为一个无效的描述符（null描述符）有助于操作系统检测和防止软件错误。如果某个程序或进程错误地尝试使用索引0的段选择器来访问内存，CPU将会检测到这个非法的段选择器
		DW		0xffff,0x0000,0x9200,0x00cf	; 第一个描述符定义了一个可以读写的数据段，上限值为0xffffffff即大小正好是4GB），基地址是0，它表示的是CPU所能管理的全部内存本身。段的属性设为0x4092，
		DW		0xffff,0x0000,0x9a28,0x0047	; 第二个描述符定义了一个可执行的代码段，它的大小是512KB，基地址是0x280000   也就是BOTPAK的位置，即bootpack c语言代码位置
		;无扩展界限（Granularity = 0）：界限以1字节为单位，最大可以定义的段大小为0xFFFF（65,535字节或64KB）。 扩展界限（Granularity = 1）：界限以4KB（4096字节）为单位，最大可以定义的段大小为0xFFFFF（1,048,575字节乘以4KB，等于4GB）。
		DW		0
GDTR0:		;该寄存器的低16位1（即内存的最初2个字节）是段上限，它等于“GDT的有效字节数 - 1”。今后我们还会偶尔用到上限这个词，意思都是表示量的大小，一般为“字节数 - 1”。剩下的高32位（即剩余的4个字节），代表GDT的开始地址。 对于一个多位数字组成的数，靠近右边的位称为低位。反之，靠近左边的位称为高位。在最初执行这个函数的时候，DWORD[ESP + 4]里存放的是段上限，DWORD[ESP+8]里存放的是地址。
		DW		8*3-1  ;在使用DW 8*3-1来定义全局描述符表寄存器（GDTR）中的表界限时，减一的操作是基于全局描述符表（GDT）的特定硬件要求。这个表界限字段不是指GDT的实际字节数，而是GDT的最后一个字节相对于GDT起始地址的偏移量。
		DD		GDT0  ;DD GDT0：定义了一个双字（Double Word），指向GDT的开始位置GDT0。DD是为双字（32位）保留空间的指令。

		ALIGNB	16
bootpack:
